/**
 * @author Nicolas Franzen
 * @namespace ColorDefinitionComposition -> CODECO
 */

var CODECO = CODECO || {};

CODECO.DefaultConfiguration = {
		width : 600,
		height: 450,
		gui : true,
		colorFolder:true,
		transformFolder: true,
		hslCircle : true,
		initColor : "#808080",
		initSegments : 10,
		rotationX : 18.0,
		rotationY : 0.0,
		animating : 0.0,
		circleHeight : 50
};

CODECO.AppState = {
		changed : false,
		mousePos : {
			x : 0,
			y : 0
		},
		mousePressed : false,
		mouseReleased: false, 
		changer : function( )
	    {
			CODECO.AppState.changed = true;
	    },
	    mouseMove : function(e)
	    {
			CODECO.AppState.mousePos.x = e.clientX;
			CODECO.AppState.mousePos.y = e.clientY;
	    },
	    mousePress : function(){
	    	CODECO.AppState.mousePressed = true;
	    },
	    mouseRelease : function(){
	    	CODECO.AppState.mouseReleased = true;
	    }
};

CODECO.RGBCubeShader = function(segments, cubeSize){

	return {
		
		uniforms: {

			"segments" :  { type: "f", value: segments },
			"cubeSize" : { type: "f", value: cubeSize}

		},
		vertexShader: [

		    "uniform float segments;",
			"uniform float cubeSize;",
			"varying vec3 vPos;",

			"void main() {",

				"vPos = position / (cubeSize * 0.5);",
				"vPos = (vPos + vec3(1,1,1))*0.5;",
				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",

			"}"

		].join("\n"),

		fragmentShader: [

		                 
			"uniform float segments;",
			"varying vec3 vPos;",
			"void main() {",

				"vec3 color = vPos;",
				"float offset = 1.0 / (segments+1.0);",
				"color.r = floor(segments * color.r)/segments + offset;",
				"color.g = floor(segments * color.g)/segments + offset;",
				"color.b = floor(segments * color.b)/segments + offset;",
				"gl_FragColor = vec4(color, 1.0);",

			"}"

		].join("\n")

	};

};

CODECO.HSLCylinderShader = function(segments, cylinderSize){

	return {
		
		uniforms: {

			"segments" :  { type: "f", value: segments },
			"cylinderSize" : { type: "f", value: cylinderSize}

		},
		vertexShader: [

		    "uniform float segments;",
			"uniform float cylinderSize;",
			"varying vec3 vPos;",

			"void main() {",

				"vPos = position / (cylinderSize * 0.5);",
				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",

			"}"

		].join("\n"),

		fragmentShader: [

		    "#define M_PI 3.1415926535897932384626433832795",             
			"uniform float segments;",
			"varying vec3 vPos;",
			"",
			"float hue2rgb(float p, float q, float t) {",
				"if(t < 0.0) t+=1.0;",
				"if(t > 1.0) t-=1.0;",
				"if(t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;",
				"if(t < 1.0 / 2.0) return q;",
				"if(t < 2.0 / 3.0) return p + (q - p) * 6.0 * (2.0/3.0 - t);",
				"return p;",
			"}",
			"",
			"void main() {",

				"vec3 color;",
				"vec2 direction = normalize(vec2(vPos.x, vPos.z));",
				
				"float saturation = sqrt(vPos.x*vPos.x+vPos.z*vPos.z);",
				"float luminance = (vPos.y+1.0)*0.5;",
				"float hue = (atan(direction.x, direction.y)+M_PI) / (2.0*M_PI);",
				
				"saturation =	floor(saturation * segments) / segments;",
				"luminance = 	floor(luminance * segments) / segments;",
				"hue =			floor(hue * segments) / segments;",
				
				"if(saturation == 0.0) {",
					"color = vec3(luminance, luminance, luminance);",
				"}",
				"else {",
					"color = vec3(0.0, 0.0, 0.0);",
					"float p = luminance <= 0.5 ? luminance * ( 1.0 + saturation ) : luminance + saturation - ( luminance * saturation );",
					"float q = ( 2.0 * luminance ) - p;",
					"color.r = hue2rgb( q, p, hue + 1.0 / 3.0 );",
					"color.g = hue2rgb( q, p, hue );",
					"color.b = hue2rgb( q, p, hue - 1.0 / 3.0 );",
				"}",
				"gl_FragColor = vec4(color, 1.0);",

			"}"

		].join("\n")

	};

};

CODECO.ColorElement = function (rgbMesh, hslMesh, cubeDim, handler, rgbOutline, hslOutline) {
	this.meshMaterial = rgbMesh.material;
	this.rgbMesh = rgbMesh;
	this.hslMesh = hslMesh;
	this.handler = handler;
	this.cubeDim = cubeDim;
	this.rgbOutline = rgbOutline;
	this.hslOutline = hslOutline;
	
};

CODECO.ColorElement.prototype = {
		updateMeshPosition : function () {
			
			this.rgbMesh.position.x = (this.meshMaterial.color.r * 2.0 - 1.0) * this.cubeDim * 0.5;
			this.rgbMesh.position.y = (this.meshMaterial.color.g * 2.0 - 1.0) * this.cubeDim * 0.5;
			this.rgbMesh.position.z = (this.meshMaterial.color.b * 2.0 - 1.0) * this.cubeDim * 0.5;
			
			if(this.rgbOutline !== undefined && this.rgbOutline !== null){
				this.rgbOutline.position.x = this.rgbMesh.position.x;
				this.rgbOutline.position.y = this.rgbMesh.position.y;
				this.rgbOutline.position.z = this.rgbMesh.position.z;
			}
			
			var hsl = this.meshMaterial.color.getHSL();
			this.hslMesh.position.x = Math.cos((hsl.h * 2.0 + 0.5) * Math.PI) * this.cubeDim * 0.5 * hsl.s;
			this.hslMesh.position.z = -Math.sin((hsl.h * 2.0 + 0.5) * Math.PI) * this.cubeDim * 0.5 * hsl.s;
			this.hslMesh.position.y = (hsl.l * 2.0 - 1.0) * this.cubeDim * 0.5;
			
			if(this.hslOutline !== undefined && this.hslOutline !== null){
				this.hslOutline.position.x = this.hslMesh.position.x;
				this.hslOutline.position.y = this.hslMesh.position.y;
				this.hslOutline.position.z = this.hslMesh.position.z;
			}
			
		},
		
		dispose : function() {
			this.rgbMesh.material.dispose();
			this.rgbMesh.dispose();
			this.hslMesh.dispose();
			if(this.rgbOutline !== undefined && this.rgbOutline !== null){
				this.rgbOutline.dispose();
			}
			if(this.hslOutline !== undefined && this.hslOutline !== null){
				this.hslOutline.dispose();
			}
		}
};

CODECO.RGBCubeLab = function (canvasContainerId, configuration) {
	this.canvasContainerId = canvasContainerId;
	var conf = CODECO.DefaultConfiguration;
	if(configuration !== null && typeof configuration === 'object')
	{
		conf = configuration;
		for (property in CODECO.DefaultConfiguration) {
	        if(conf[property] === undefined || conf[property] === null)
	        	conf[property] = CODECO.DefaultConfiguration[property];
	    }
	}
	
	for (property in conf) {
        console.log(property + " = " +conf[property]);
    }
	
	this.clock = new THREE.Clock(true);
	this.initColor = conf.initColor;
	this.cubeDim = 120;
	
	var offset = this.cubeDim * 0.66;
	
	this.renderer = new THREE.WebGLRenderer();
	this.renderer.setSize( conf.width, conf.height );
	this.renderer.setClearColor(0xa5a5a5);
	
	document.getElementById(canvasContainerId).appendChild(this.renderer.domElement);
	
	this.scene = new THREE.Scene();
	
	this.camera = new THREE.PerspectiveCamera( 70, conf.width / conf.height, 1, 1000 );
	this.camera.position.z = 200;
	
	this.rgbCubeGeometry = new THREE.CubeGeometry(this.cubeDim, this.cubeDim, this.cubeDim);
	this.rgbCubeMaterial = new THREE.ShaderMaterial(CODECO.RGBCubeShader(conf.initSegments, this.cubeDim));
	this.rgbCubeMaterial.side = THREE.BackSide;
	this.rgbCube 	= new THREE.Mesh(this.rgbCubeGeometry, this.rgbCubeMaterial);
	this.rgbCube.position.x -= offset;
	this.rgbCube.position.x += 20;

	this.hslCylinderGeometry = new THREE.CylinderGeometry(this.cubeDim*0.5, this.cubeDim*0.5, this.cubeDim, 50, 50, false);
	this.hslCylinderMaterial = new THREE.ShaderMaterial(CODECO.HSLCylinderShader(conf.initSegments, this.cubeDim));
	this.hslCylinderMaterial.side = THREE.BackSide;
	this.hslCylinder = new THREE.Mesh(this.hslCylinderGeometry, this.hslCylinderMaterial);
	this.hslCylinder.position.x += offset;
	this.hslCylinder.position.x += 10;
	
	this.rgbCube.rotation.x = this.hslCylinder.rotation.x = Math.PI * 0.1;
	
	this.outlineMaterial = new THREE.MeshBasicMaterial({color: new THREE.Color("#000000"), side : THREE.BackSide });
	
	this.circleGeom = new THREE.CircleGeometry(this.cubeDim*0.5, 50);
	this.circleGeom.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI * 0.5) );
	this.circle = new THREE.Mesh(this.circleGeom, this.hslCylinderMaterial);
	
	this.sphereGeometry = new THREE.SphereGeometry(this.cubeDim*0.05, 50, 50);

	
	this.addedColors = [];
	
	var labRef = this;
	
	this.guiData = {
		rotationX : conf.rotationX,
		rotationY : conf.rotationY,
		segments : conf.initSegments,
		animating : conf.animating,
		pushColor : function (){labRef.pushColor();},
		popColor : function (){labRef.popColor();},
		colors : [],
		circleHeight : conf.circleHeight
	};
	
	this.isResizing = false;
	this.rendererRect = this.renderer.domElement.getBoundingClientRect();
	
	
	if(conf.gui){
		var handler = null;
		
		this.gui = new dat.GUI({ autoPlace: false });
		document.getElementById(canvasContainerId).appendChild(this.gui.domElement);
		this.gui.domElement.style.position = "absolute";
		
		if(conf.colorFolder){
			this.colors = this.gui.addFolder("Colors");
			handler = this.colors.add( this.guiData, 'pushColor');
			handler.name("add color");	
			
			handler = this.colors.add( this.guiData, 'popColor');
			handler.name("remove last color");
		}
		
		if(conf.transformFolder){
			var view = this.gui.addFolder("View");
			handler = view.add( this.guiData, 'rotationX', -180, 180);
			handler.onChange(CODECO.AppState.changer);	 
			
			handler = view.add( this.guiData, 'rotationY', -180, 180);
			handler.onChange(CODECO.AppState.changer);	
			handler.listen();
			
			handler = view.add( this.guiData, 'segments', 8, 256);
			handler.onChange(CODECO.AppState.changer);	
			
			handler = view.add( this.guiData, 'animating', -90.0, 90.0);
			handler.onChange(CODECO.AppState.changer);
		}
		
		if(conf.hslCircle){
			handler = this.gui.add( this.guiData, 'circleHeight', 0, 100);
			handler.onChange(CODECO.AppState.changer);
		}
		this.updateGUIPosition();
	}
	
	this.updateCircleHeight();
	
	this.scene.add(this.rgbCube );
	this.scene.add(this.hslCylinder );
	this.hslCylinder.add(this.circle);

};

CODECO.RGBCubeLab.prototype = {
		
		constructor : CODECO.RGBCubeLab,

		updateGUIPosition : function() {
			var guiRect = this.gui.domElement.getBoundingClientRect();
			this.gui.domElement.style.top = this.rendererRect.top + "px";
			this.gui.domElement.style.left = (this.rendererRect.left+this.rendererRect.width-guiRect.width) + "px";
		},
		
		updateCircleHeight : function () {
			
			//this.guiData.circleHeight = Math.floor(this.guiData.circleHeight / 100.0 * this.guiData.segments)/ this.guiData.segments * 100;
			var y = (this.guiData.circleHeight-50)/100.0 * this.cubeDim;
			for(var i = 0; i < this.circleGeom.vertices.length; ++i)
				this.circleGeom.vertices[i].y = y;
			
			this.circleGeom.verticesNeedUpdate = true;
			
		},
		
		update : function() {	
			
			if(this.guiData.animating != 0.0) {
				this.guiData.rotationY += this.clock.getDelta() * this.guiData.animating;
				if(this.guiData.rotationY > 180.0)
					this.guiData.rotationY -= 360.0;
				else if(this.guiData.rotationY < -180.0)
					this.guiData.rotationY += 360.0;
			}
			
			if(this.guiData.animating != 0.0 || CODECO.AppState.changed) {
				this.hslCylinder.rotation.y = this.rgbCube.rotation.y = this.guiData.rotationY / 180.0 * Math.PI;
				this.hslCylinder.rotation.x = this.rgbCube.rotation.x = this.guiData.rotationX / 180.0 * Math.PI;
			}
			
			if(CODECO.AppState.changed){
				this.hslCylinderMaterial.uniforms.segments.value = this.guiData.segments;
				this.rgbCubeMaterial.uniforms.segments.value = this.guiData.segments;
				
				for(var i = 0; i < this.guiData.colors.length; ++i){
					this.addedColors[i].meshMaterial.color.set(this.guiData.colors[i].value);
					this.addedColors[i].updateMeshPosition();
				}
				
				this.updateCircleHeight();
				
				CODECO.AppState.changed = false;
			}
			
			var cornerX = this.rendererRect.left + this.rendererRect.width;
			var cornerY = this.rendererRect.top + this.rendererRect.height;
			
			if((cornerX - 10 <= CODECO.AppState.mousePos.x && CODECO.AppState.mousePos.x <= cornerX + 5 &&
					cornerY - 10 <= CODECO.AppState.mousePos.y && CODECO.AppState.mousePos.y <= cornerY + 5) || this.isResizing)
				this.renderer.domElement.style.cursor = "nw-resize";
			else
				this.renderer.domElement.style.cursor = "default";
			
			if(CODECO.AppState.mousePressed) {
				if(!this.isResizing){
					if(cornerX - 10 <= CODECO.AppState.mousePos.x && CODECO.AppState.mousePos.x <= cornerX + 5 &&
							cornerY - 10 <= CODECO.AppState.mousePos.y && CODECO.AppState.mousePos.y <= cornerY + 5)
						this.isResizing = true;
				}
				if(this.isResizing) {
					this.rendererRect = this.renderer.domElement.getBoundingClientRect();
					var width = CODECO.AppState.mousePos.x - this.rendererRect.left + 5;
					var height = CODECO.AppState.mousePos.y - this.rendererRect.top + 5;
					this.renderer.setSize( width, height );
					this.camera.aspect	= width / height;
					this.camera.updateProjectionMatrix();
					this.render();
					if(this.gui !== undefined && this.gui !== null)
						this.updateGUIPosition();
				}
			}
			
			if(CODECO.AppState.mouseReleased) {
				CODECO.AppState.mouseReleased = false;
				CODECO.AppState.mousePressed = false;
				if(this.isResizing) {
					this.isResizing = false;
				}
			}
			
		},
		
		render : function() {
			this.renderer.render(this.scene, this.camera);
		},
		
		pushColor : function() {
			var material = new THREE.MeshBasicMaterial({color: new THREE.Color(this.initColor) });
			var rgbSphere = new THREE.Mesh(this.sphereGeometry, material);
			var hslSphere = new THREE.Mesh(this.sphereGeometry, material);
			var rgbOutline = new THREE.Mesh(this.sphereGeometry, this.outlineMaterial);
			var hslOutline = new THREE.Mesh(this.sphereGeometry, this.outlineMaterial);
			
			rgbOutline.scale.x = rgbOutline.scale.y = rgbOutline.scale.z = 
				hslOutline.scale.x = hslOutline.scale.y = hslOutline.scale.z = 1.1;
			
			this.rgbCube.add(rgbSphere);
			this.rgbCube.add(rgbOutline);
			this.hslCylinder.add(hslSphere);
			this.hslCylinder.add(hslOutline);
			
			this.guiData.colors.push({value : this.initColor});
			var handler = null;
			
			if(typeof this.colors === 'object' && this.colors !== null) {
				handler = this.colors.addColor(this.guiData.colors[this.guiData.colors.length-1], 'value');
				handler.onChange(CODECO.AppState.changer);
				handler.name("color " + (this.guiData.colors.length-1));
			}
			
			var element = new CODECO.ColorElement(rgbSphere, hslSphere, this.cubeDim, handler, rgbOutline, hslOutline);
			element.updateMeshPosition();
			this.addedColors.push(element);
		},
		
		popColor : function() {
			
			if(this.addedColors.length == 0)
				return;
			
			var element = this.addedColors.pop();
			this.guiData.colors.pop();
			
			this.rgbCube.remove(element.rgbMesh);
			this.hslCylinder.remove(element.hslMesh);
			
			if(element.handler !== null)
				element.handler.remove();
			element.dispose();
			
		}
};

CODECO.RotatingRGBCube = function (canvasContainerId) {
	this.canvasContainerId = canvasContainerId;
	
	var width = 600, height = 480;
	
	this.renderer;
	this.renderer = new THREE.WebGLRenderer();
	this.renderer.setSize( width, height );
	this.renderer.setClearColor(0xa5a5a5);
	
	document.getElementById(canvasContainerId).appendChild(this.renderer.domElement);
	
	this.scene = new THREE.Scene();
	
	this.camera = new THREE.PerspectiveCamera( 70, width / height, 1, 1000 );
	this.camera.position.z = 200;
	
	this.cubeDim = 120;
	this.rgbCubeGeometry = new THREE.CubeGeometry(this.cubeDim, this.cubeDim, this.cubeDim);
	this.rgbCubeMaterial = new THREE.ShaderMaterial(CODECO.RGBCubeShader(8.0, this.cubeDim));
	this.rgbCubeMaterial.side = THREE.BackSide;
	this.rgbCube 	= new THREE.Mesh(this.rgbCubeGeometry, this.rgbCubeMaterial);

	
	this.clock = new THREE.Clock(true);
	this.degreesPerSecond = Math.PI / 180.0 * 8.0;
	
	this.scene.add(this.rgbCube );
	
};

CODECO.RotatingRGBCube.prototype = {
		
		constructor : CODECO.RotatingRGBCube,

		
		update : function() {	
			var deltaTime = this.clock.getDelta();
			this.rgbCube.rotation.y += this.degreesPerSecond * deltaTime;
			//this.rgbCube.rotation.x += Math.PI / 180.0 * 7.0 * deltaTime;
		},
		
		render : function() {
			this.renderer.render(this.scene, this.camera);
		}
		
};

CODECO.ColorWheel = function (canvasContainerId) {
	this.canvasContainerId = canvasContainerId;
	
	var width = 600, height = 480;
	
	this.renderer;
	this.renderer = new THREE.WebGLRenderer();
	this.renderer.setSize( width, height );
	this.renderer.setClearColor(0xa5a5a5);
	
	document.getElementById(canvasContainerId).appendChild(this.renderer.domElement);
	
	this.scene = new THREE.Scene();
	
	this.camera = new THREE.PerspectiveCamera( 70, width / height, 1, 1000 );
	this.camera.position.z = 100;
	
	this.dim = 120;
	
	this.hslCylinderMaterial = new THREE.ShaderMaterial(CODECO.HSLCylinderShader(200.0, this.dim));
	this.hslCylinderMaterial.side = THREE.BackSide;
	
	this.circleGeom = new THREE.CircleGeometry(this.dim*0.5, 50);
	this.circleGeom.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI * 0.5) );
	
	this.circle = new THREE.Mesh(this.circleGeom, this.hslCylinderMaterial);
	this.circle.rotation.x += Math.PI * 0.5;
	
	this.scene.add(this.circle );
	
};

CODECO.ColorWheel.prototype = {
		
		constructor : CODECO.ColorWheel,

		
		update : function() {	
		},
		
		render : function() {
			this.renderer.render(this.scene, this.camera);
		}
		
};
